package mars.ColorPiano;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;

public class ColorPiano implements MouseListener {
	/**
	 * @category data
	 *  collections of already displayed colors (to display random but unique colors)
	 */
	private boolean[] mUsedColors = new boolean[ShapePanel.Shape.values().length];
	
	/**
	 * @category data
	 *  collections of already displayed shapes (to display random but unique shapes)
	 */
	private boolean[] mUsedShapes = new boolean[ShapePanel.colors.length];
	
	/**
	 * @category data
	 *  shape panels that are displayed in game
	 */
	private ShapePanel[] mShapes = new ShapePanel[4];
	
	private int mSuccessCount;
	private int mFailCount;
	
	/**
	 * @category data
	 * MidiPlayer will play different sounds on successful and fail clicks 
	 */
	private MidiPlayer mMidiPlayer = new MidiPlayer();
	
	/**
	 * @category data
	 * Thread that waits for round timeout 
	 */
	private Thread mRoundThread = null;
	
	/**
	 * @category data
	 * Displays current round left timeout 
	 */
	private ColoredLabel mTimerLabel;
	
	/**
	 * @category data
	 * Displays task and finished round result 
	 */
	private ColoredLabel mTaskLabel;
	
	/**
	 * @category inner_classes
	 * Task is automatically generated combination of shape and color to compare with shape, really clicked by user
	 */
	private class Task {
		private ShapePanel.Shape mShape;
		private Color mColor; 
		private String mTaskText;
		
		public Task(ShapePanel.Shape shape, Color color) {
			mShape = shape;
			mColor = color;
			mTaskText = createTaskText();
		}

		private String createTaskText() {
			String taskText = "Click on ";
			if (mColor.equals(Color.red)) {
				taskText += "red"; 
			} else if (mColor.equals(Color.green)) {
				taskText += "green"; 
			} else if (mColor.equals(Color.blue)) {
				taskText += "blue"; 
			}  else if (mColor.equals(Color.yellow)) {
				taskText += "yellow"; 
			} else if (mColor.equals(Color.orange)) {
				taskText += "orange"; 
			}
			
			taskText += " " + mShape.toString().toLowerCase();
			
			return taskText;
		}
	
		
		/**
		 * checks if selected shape corresponds task
		 * @param shapePanel panel to compare with
		 * @return true if shape and color is the same, false otherwise
		 */
		public boolean checkTask(ShapePanel shapePanel) {
			return (shapePanel.shape().equals(mShape) && shapePanel.color().equals(mColor));
		}
		
		public String taskText() {
			return mTaskText;
		}
	}
	private Task mTask = null;
	
	/**
	 * program main where we instantiate game class and run it
	 * @param args
	 */
	public static void main(String[] args) {
		final ColorPiano game = new ColorPiano();
		game.go();
	}
	
	/**
	 * @return unique random shape, so there will be no other same shape in game screen
	 */
	private mars.ColorPiano.ShapePanel.Shape makeRandomShape() {
		int shapeNumber = 0;
		
		do {
			shapeNumber = (int)(Math.random() * mUsedShapes.length);
		} while ( mUsedShapes[shapeNumber] );
		mUsedShapes[shapeNumber] = true;
		
		return mars.ColorPiano.ShapePanel.Shape.values()[shapeNumber];
	}
	
	/**
	 * @return unique random color, so there will be no other shape with the same color
	 */
	private Color makeRandomColor() {
		int colorNumber = 0;
		
		do {
			colorNumber = (int)(Math.random() * mUsedColors.length);
		} while ( mUsedColors[colorNumber] );
		mUsedColors[colorNumber] = true;
		
		return ShapePanel.colors[colorNumber];
	}
	
	
	/**
	 * main gui construction. here we create frame, layout of game, initial shapes for first round and text messages to instruct user what to do 
	 */
	public void go()
	{
//		creating gui
		JFrame frame = new JFrame();
		frame.setSize(600, 400);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		Dimension preferredSize = new Dimension(frame.getHeight()/4, frame.getHeight()/4);
		Color backgroundColor = Color.darkGray;
		
//		creating shape panels with random colors and shapes.
//		they will be stacked into two panels, north and south.
		BorderLayout northLayout = new BorderLayout();		// BorderLayout places controls at one of the borders (east, west, south, north) or at center of container
		JPanel northPanel = new JPanel(northLayout);
		northPanel.setBackground(backgroundColor);
		
		ShapePanel northWest = new ShapePanel(makeRandomShape(), makeRandomColor(), backgroundColor);
		mShapes[0] = northWest;
		northWest.setPreferredSize(preferredSize);
		northPanel.add(BorderLayout.WEST, northWest);
		
		ShapePanel northEast = new ShapePanel(makeRandomShape(), makeRandomColor(), backgroundColor);
		mShapes[1] = northEast;
		northEast.setPreferredSize(preferredSize);
		northPanel.add(BorderLayout.EAST, northEast);
		
		mTimerLabel = new ColoredLabel("Make first round task", Color.white, northPanel.getHeight());
		northPanel.add(BorderLayout.CENTER, mTimerLabel);
		
		frame.getContentPane().add(BorderLayout.NORTH, northPanel);
		
//		south panel 
		BorderLayout southLayout = new BorderLayout();
		JPanel southPanel = new JPanel(southLayout);
		southPanel.setBackground(backgroundColor);
		
		ShapePanel southWest = new ShapePanel(makeRandomShape(), makeRandomColor(), backgroundColor);
		mShapes[2] = southWest; 
		southWest.setPreferredSize(preferredSize);
		southPanel.add(BorderLayout.WEST, southWest);
		
		ShapePanel southEast = new ShapePanel(makeRandomShape(), makeRandomColor(), backgroundColor);
		mShapes[3] = southEast;
		southEast.setPreferredSize(preferredSize);
		southPanel.add(BorderLayout.EAST, southEast);
		
		frame.getContentPane().add(BorderLayout.SOUTH, southPanel);
		
//		select random task for first round to be displayed in central panel
		selectRandomTask();
		
//		central panel with task text
		JPanel centralPanel = new JPanel(new BorderLayout());
		centralPanel.setBackground(backgroundColor);
		mTaskLabel = new ColoredLabel(mTask.taskText(), Color.white, centralPanel.getHeight());
		centralPanel.add(BorderLayout.CENTER, mTaskLabel);
		frame.getContentPane().add(BorderLayout.CENTER, centralPanel);
		
//		locate window frame in the center of screen
		Dimension screenSize= Toolkit.getDefaultToolkit().getScreenSize();
		frame.setLocation(screenSize.width/2 - frame.getWidth()/2, screenSize.height/2 - frame.getHeight()/2);
		frame.setResizable(false);
		frame.setVisible(true);
		
//		add mouse listener for all shapes to handle clicks on shapes
		for (ShapePanel shape : mShapes) {
			shape.addMouseListener(this);
		}
	}
	
	private void selectRandomTask()
	{
		int taskIdx = (int)(mShapes.length * Math.random());
		mTask = new Task(mShapes[taskIdx].shape(), mShapes[taskIdx].color());
	}
	
	private void nextRound() {
//		reset used values
		for (int i = 0; i < mUsedColors.length; ++i) {
			mUsedColors[i] = false;
		}
		
		for (int i = 0; i < mUsedShapes.length; ++i) {
			mUsedShapes[i] = false;
		}
//		create new values
		for (ShapePanel shape : mShapes) {
			shape.setColor(makeRandomColor());
			shape.setShape(makeRandomShape());
			shape.repaint();
		}
		
		selectRandomTask();
		mTaskLabel.setText(mTask.taskText());
	}

	private void greyAllShapes() {
		for (ShapePanel shapePanel : mShapes) {
			shapePanel.setColor(Color.gray);
			shapePanel.repaint();
		}
	}
	
	private boolean firstRound = true;
	private void roundFinishedMessage(boolean succeed) {
//		synchronized (mTaskLabel) {
			mTaskLabel.setText("Round finished. You " + ( succeed ? "succeed" : "failed" ) + " (" + mSuccessCount + ":" + mFailCount + ")");
//		}
	}
	
	boolean mSuspend = false;
	@Override
	public void mouseClicked(MouseEvent e) {
		boolean succeed = mTask.checkTask((ShapePanel)e.getSource());
		
		if (succeed) {
			mMidiPlayer.playSound(true);
			++mSuccessCount;
		} else {
			mMidiPlayer.playSound(false);
			++mFailCount;
		}
		
		roundFinishedMessage(succeed);
		
		mTask = null;
		greyAllShapes();
		
		try {
			System.out.println("mSuspend=true. Sleeping 2 sec");
			Thread.sleep(2000);
			mSuspend = false;
			System.out.println("mSuspend=false");
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
//		we create thread for changing round tasks on first round after player made first round task (that is not limited by timer)
//		if ( !firstRound )
//			return;
		
		if (mRoundThread != null)
			mRoundThread.interrupt();
		
//		try {
//			mRoundThread.join();
//		} catch (InterruptedException e1) {
//			// TODO Auto-generated catch block
//			e1.printStackTrace();
//		}
		
		mRoundThread = new Thread( new NextRoundRunnable());
		mRoundThread.start();
		firstRound = false;
	}

	@Override
	public void mousePressed(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}
	
	String makeTimerText(int round) {
		return "round " + round + ". succeed: " + mSuccessCount + ". failed: " + mFailCount;
	}
	
	private class NextRoundRunnable implements Runnable {

		@Override
		public void run() {
			int round = 0;
//			repeat rounds (that are updated on timer) for selected number of rounds
//			we need check if player clicked shapes or not to make auto count of fails after round timer stops
			
//			do { // rounds left
				//			we start next round
				nextRound();
				int prevSuccessCount = mSuccessCount;
				int prevFailCount = mFailCount;
				
				//			and begin timer countdown
				int increment = 1000;
				int timerInterval = 5000;
				int timer = timerInterval;

				do { // timer ticks
					mTimerLabel.setText("time left: " + timer/ 1000);
					
					try {
						Thread.sleep(increment);
						if (!mSuspend) {
							timer -= increment;
							System.out.println("!mSuspend: timer decrement = " + timer);
						}
						else {
							System.out.println("mSuspend: no timer decrement");
						}
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
				} while (timer > 0); // timer ticks

				if (mTask == null) 
					return;
				
//				mTimerLabel.setText(makeTimerText(round));
				
				boolean succeed = (mSuccessCount > prevSuccessCount);
				if ( !succeed && (mFailCount == prevFailCount ) ) {
					++mFailCount;
				}
				
				roundFinishedMessage(succeed);
			
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
//			} while (++round <= 3); // rounds left
			
			greyAllShapes();
			mTaskLabel.setText("Game Finished. You succeed " + mSuccessCount + " times");
		}

	}
}
